package vin.pth.authentication.filter;

import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import pth.authentication.config.AuthenticationProperties;
import pth.authentication.context.RbacContext;
import pth.authentication.service.AuthenticationUserService;
import pth.authentication.service.RbacService;
import vin.pth.authentication.handler.AuthenticationHandler;
import vin.pth.base.context.UserDetailsContext;
import vin.pth.base.exception.authentication.AuthenticationException;
import vin.pth.base.pojo.DefaultUserDetails;
import vin.pth.base.pojo.UserDetails;


/**
 * 鉴权过滤器.
 *
 * @author cocoon
 */
@Slf4j
@RequiredArgsConstructor
public class AuthenticationFilter implements Filter {

  private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

  private final AuthenticationUserService authenticationUserService;
  private final RbacService rbacService;
  private final AuthenticationHandler authenticationHandler;
  @Resource
  private AuthenticationProperties authenticationProperties;

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    try {
      // 校验包含在白名单列表中
      if (!containsWhiteList(httpRequest.getRequestURI())) {
        String token = httpRequest.getHeader(authenticationProperties.getTokenName());
        UserDetailsContext.setUserDetails(DefaultUserDetails.anonymousUser());
        if (StringUtils.hasText(token)) {
          UserDetails userDetails = authenticationUserService.getByToken(token);
          UserDetailsContext.setUserDetails(userDetails);
        }
        RbacContext rbacContext = buildRbacContext(httpRequest);
        rbacService.checkPermission(rbacContext);
      }

      chain.doFilter(request, response);
    } catch (AuthenticationException e) {
      authenticationHandler.failureHandler((HttpServletRequest) request,
          (HttpServletResponse) response, e);
    } finally {
      // 无论过程如何，都需要清理掉当前线程变量中的用户信息
      UserDetailsContext.clear();
    }


  }

  private RbacContext buildRbacContext(HttpServletRequest httpRequest) {
    RbacContext context = new RbacContext();
    context.setMethod(httpRequest.getMethod());
    context.setUrl(httpRequest.getRequestURL().toString());
    context.setPath(httpRequest.getRequestURI());
    context.setToken(httpRequest.getHeader(authenticationProperties.getTokenName()));
    context.setExtra(httpRequest);
    context.setUserDetails(UserDetailsContext.getUserDetails());
    return context;
  }

  /**
   * 是否包含于白名单.
   *
   * @param uri URI
   * @return true=包含
   */
  private boolean containsWhiteList(String uri) {
    if (!CollectionUtils.isEmpty(authenticationProperties.getWhiteList())) {
      for (String s : authenticationProperties.getWhiteList()) {
        if (ANT_PATH_MATCHER.match(s, uri)) {
          log.info("{}:路径属于白名单路径{}，放行", uri, s);
          return true;
        }
      }
    }
    return false;
  }


}
